/**
 * 
 */
package com.tkiicpp.tank.utils.dependence;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

/**
 * @author <a href="mailto:tanktong@gmail.com">Tank.Tong</a>
 * @version 1.0 2010-08-25 16:43:48
 * @since 1.0
 */
public class DependenceTree {
	
	private final JarNode root;
	private Map<String, JarNode> jarNodeMap = new HashMap<String, JarNode>();
	private Map<String, List<ClassNode>> classNodeMap = new HashMap<String, List<ClassNode>>();
	
	public DependenceTree(String jarName) {
		this.root = new JarNode(jarName, this);
		jarNodeMap.put(jarName, root);
	}
	
	public JarNode getRoot() {
		return root;
	}
	
	public void addClassNode(ClassNode node) {
		List<ClassNode> nodes = classNodeMap.get(node.className);
		
		if (null == nodes) {
			nodes = new ArrayList<ClassNode>();
			classNodeMap.put(node.className, nodes);
		}
		
		nodes.add(node);
	}
	
	public Map<String, List<ClassNode>> getAllClassNodes() {
		return classNodeMap;
	}
	
	public boolean containsClassNode(String className) {
		return classNodeMap.containsKey(className);
	}
	
	public List<ClassNode> getClassNode(String className) {
		return classNodeMap.get(className);
	}
	
	public JarNode getJarNode(String jarName) {
		JarNode jarNode = jarNodeMap.get(jarName);
		
		if (null == jarNode) {
			jarName = null == jarName ? "NULL" : jarName;
			jarNode = new JarNode(jarName, this);
			jarNodeMap.put(jarName, jarNode);
		}
		
		return jarNode; 
	}

	public int getJarNodeSize() {
		return jarNodeMap.values().size();
	}
	
	public Map<String, JarNode> getAllJarNodes() {
		return jarNodeMap;
	}
	
	public void link() {
		List<ClassNode> nodeList = null;
		for (List<ClassNode> classNodes : classNodeMap.values()) {
			for (ClassNode node : classNodes) {
				for (String className : node.dependenceClassNames) {
					nodeList = classNodeMap.get(className);
					if (null != nodeList) {
						node.dependenceNodes.put(className, nodeList);
						for (ClassNode dep : nodeList) {
							if (dep == node) {
								continue;
							}
							
							dep.dependeByNodes.add(node);
						}
						
						for (ClassNode depNode : nodeList) {
							node.getJarNode().addDependenceLine(
									new DependenceLine(node, depNode));
						}
					}
				}
			}
		}
	}

	public class ClassNode {
		private final DependenceTree tree;
		private final String className;
		private JarNode jar;
		private Set<ClassNode> dependeByNodes = new HashSet<ClassNode>();
		private Map<String, List<ClassNode>> dependenceNodes = new HashMap<String, List<ClassNode>>();
		private List<String> dependenceClassNames = new ArrayList<String>();
		
		public ClassNode(String className, DependenceTree tree, JarNode jarNode) {
			this.className = className;
			this.tree = tree;
			this.jar = jarNode;
			
			tree.addClassNode(this);
			if (null != jarNode) {
				jarNode.classNodes.add(this);
			}
			
		}
		
		public void addDependenceClassName(String className) {
			dependenceClassNames.add(className);
		}
		
		public Set<ClassNode> getDependeByClassNodes() {
			return this.dependeByNodes;
		}
		
		public String getClassName() {
			return this.className;
		}
		
		public List<String> getDependenceClasses() {
			return dependenceClassNames;
		}
		
		public List<ClassNode> getDependenceClassNode(String className) {
			return dependenceNodes.get(className);
		}
		
		public JarNode getJarNode() {
			return this.jar;
		}
		
	}
	
	public class JarNode {
		private final String jarName;
		private final DependenceTree tree;
		private List<ClassNode> classNodes = new ArrayList<ClassNode>();
		private List<DependenceLine> dependenceLines = new ArrayList<DependenceLine>();
		private Map<JarNode, Set<ClassNode>> directDependenceJars = new HashMap<JarNode, Set<ClassNode>>();
		
		private JarNode(String jarName, DependenceTree tree) {
			this.jarName = jarName;
			this.tree = tree;
		}
		
		public List<ClassNode> getClassNodes() {
			return this.classNodes;
		}
		
		public String getJarName() {
			return jarName;
		}
		
		private void addDependenceLine(DependenceLine line) {
			this.dependenceLines.add(line);
			Set<ClassNode> nodes = directDependenceJars.get(line.getToNode().jar);
			
			if (null == nodes) {
				nodes = new TreeSet<ClassNode>(new Comparator<ClassNode>() {

					@Override
					public int compare(ClassNode node1, ClassNode node2) {
						return node1.getClassName().compareTo(node2.getClassName());
					}
				});
				directDependenceJars.put(line.getToNode().jar, nodes);
			}
			
			nodes.add(line.getToNode());
		}
		
		public Map<JarNode, Set<ClassNode>> directDependenceJars() {
			return this.directDependenceJars;
		}
	}
	
	public class DependenceLine {
		private final ClassNode from;
		private final ClassNode to;
		
		public DependenceLine(ClassNode from, ClassNode to) {
			this.from = from;
			this.to = to;
		}
		
		public ClassNode getFromNode() {
			return this.from;
		}
		
		public ClassNode getToNode() {
			return this.to;
		}
	}

}
